using IdentityServer4.Services;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace Devonline.Identity.Admin.Controllers;

[AllowAnonymous]
public class ExternalController(
    IIdentityServerInteractionService interaction,
    ILogger<ExternalController> logger,
    HttpSetting httpSetting,
    SignInManager<User> signInManager,
    AuthenticationService authenticationService) : Controller
{
    private readonly IIdentityServerInteractionService _interaction = interaction;
    private readonly ILogger<ExternalController> _logger = logger;
    private readonly HttpSetting _httpSetting = httpSetting;
    private readonly SignInManager<User> _signInManager = signInManager;
    private readonly AuthenticationService _authenticationService = authenticationService;

    /// <summary>
    /// initiate roundtrip to external authentication provider
    /// </summary>
    [HttpGet]
    public IActionResult Challenge(string scheme, string returnUrl)
    {
        if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/";

        // validate returnUrl - either it is a valid OIDC URL or back to a local page
        if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false)
        {
            // user might have clicked on a malicious link - should be logged
            throw new Exception("invalid return URL");
        }

        // start challenge and roundtrip the return URL and scheme 
        var props = new AuthenticationProperties
        {
            RedirectUri = _httpSetting.UserInteraction!.Callback,
            Items =
            {
                { nameof(returnUrl), returnUrl },
                { nameof(scheme), scheme },
            }
        };

        return Challenge(props, scheme);
    }
    /// <summary>
    /// Post processing of external authentication
    /// </summary>
    [HttpGet("External/Callback")]
    public async Task<IActionResult> CallbackAsync()
    {
        // read external identity from the temporary cookie
        var scheme = CookieAuthenticationDefaults.AuthenticationScheme;
        var result = await HttpContext.AuthenticateAsync(scheme);
        if (!(result?.Succeeded ?? false))
        {
            throw new Exception("External authentication error");
        }

        if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
        {
            var externalClaims = result.Principal?.Claims.Select(c => $"{c.Type}: {c.Value}");
            _logger.LogDebug("External claims: {@claims}", externalClaims);
        }

        var user = await _authenticationService.AutoProvisionUserFromExternalProviderAsync(result);
        var principal = await _signInManager.CreateUserPrincipalAsync(user);
        var claims = await _authenticationService.GetUserClaimsAsync(user);
        var isuser = new IdentityServer4.IdentityServerUser(user.Id)
        {
            DisplayName = user.UserName,
            IdentityProvider = result.Properties.Items["scheme"],
            AdditionalClaims = principal.Claims.Concat(claims).ToList()
        };

        result.Properties.IsPersistent = false;
        await HttpContext.SignInAsync(isuser, result.Properties);

        // delete temporary cookie used during external authentication
        await HttpContext.SignOutAsync(scheme);

        // retrieve return URL
        var returnUrl = _authenticationService.GetRedirectUrl(user);

        // check if external login is in the context of an OIDC request
        var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
        if (context is not null && context.IsNativeClient())
        {
            // The client is native, so this change in how to
            // return the response is for better UX for the end user.
            return this.LoadingPage("Redirect", returnUrl);
        }

        return Redirect(returnUrl);
    }
}